#pragma once

#include <iostream>
#include <vector>
#include <cassert>
#include <pthread.h>
#include <semaphore.h>

using namespace std;

/*
生产消费模型+阻塞队列+互斥锁与生产消费模型+循环队列+信号量的区别
阻塞队列不存在剩余空间数量这一说法 所以不好引入信号量
1.前者同一时刻最多只能有一个线程访问队列 后者同一时刻最多能有一个生产线程和一个消费线程访问队列
2.前者可以直接引入多生产多消费 后者引入多生产多消费时需要加锁
3.前者必须在访问资源后才能得知资源的状况 为了防止不满足访问条件时同一线程一直进行判断 需要引入条件变量
  后者可以在访问资源前得知资源的状况 当资源状况不满足访问条件时 线程会阻塞等待 不用再引入条件变量
*/

const int g_cap = 10;

template <class T>
class ringQueue
{
public:
    ringQueue(int capacity = g_cap)
    : _q(capacity),
      _capacity(capacity),
      _rear(0),
      _front(0)
    {
        sem_init(&_psem, 0, capacity);
        sem_init(&_csem, 0, 0);
        pthread_mutex_init(&_plock, nullptr);
        pthread_mutex_init(&_clock, nullptr);
    }

    ~ringQueue()
    {
        sem_destroy(&_psem);
        sem_destroy(&_csem);
        pthread_mutex_destroy(&_plock);
        pthread_mutex_destroy(&_clock);
    }

    void P(sem_t& sem)
    {
        int n = sem_wait(&sem);
        assert(n == 0);
        (void)n;
    }

    void V(sem_t& sem)
    {
        int n = sem_post(&sem);
        assert(n == 0);
        (void)n;
    }

    /*
    信号量可以有效地实现生产与消费之间的互斥与同步
    引入多生产多消费后 如果多个生产者或消费者线程能够同时进入临界区 可能导致对同一位置重复访问等问题 因此需要加锁控制
    */

    void push(const T& in)
    {
        P(_psem);//生产者能使用的空间-1 _psem > 0 -> _psem--  _psem == 0 -> 阻塞等待
        /*
        加锁在申请信号量之后
        1.信号量的加减为原子操作 本身是线程安全的 不用对其进行保护
        2.减少临界区代码量 其中一个生产线程在访问队列的同时 其他生产线程可以申请信号量
          如果加锁放到申请信号量前 则其他线程构建任务后什么都做不了 降低了处理效率
        */
        pthread_mutex_lock(&_plock);
        //将生产的数据放入队列中
        //生产的数据放到队列的哪个位置 用什么来表示下标
        _q[_rear++] = in;
        _rear %= _capacity;//控制下标边界
        pthread_mutex_unlock(&_plock);//早一点释放锁可以让其他线程提前竞争锁
        V(_csem);//消费者能消费的数据+1 _csem++
    }

    void pop(T* out)
    {
        P(_csem);//消费者能使用的数据-1
        pthread_mutex_lock(&_clock);
        //将队列中的数据输出
        *out = _q[_front++];
        _front %= _capacity;
        pthread_mutex_unlock(&_clock);
        V(_psem);
    }

private:
    vector<T> _q;
    int _capacity;
    sem_t _psem;//生产者关注剩余的空间数量
    sem_t _csem;//消费者关注剩余的数据数量
    //由于消费者线程永远不可能超过生产者线程 当两个指针重合时 必有一个指针先走 所以可以保证同一位置的生产消费不会同时进行
    int _rear;
    int _front;
    //引入两把锁 保证同一时刻最多只有一个生产者线程和消费者线程访问循环队列
    pthread_mutex_t _plock;
    pthread_mutex_t _clock;
};